"""  
 MicroPython driver for SD cards using SPI bus.  
 Requires an SPI bus and a CS pin. Provides readblocks and writeblocks  
 methods so the device can be mounted as a filesystem.  
 Example usage on pyboard:  
   import pyb, sdcard, os  
   sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5)  
   pyb.mount(sd, '/sd2')  
   os.listdir('/')  
 Example usage on ESP8266:  
   import machine, sdcard, os  
   sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15))  
   os.mount(sd, '/sd')  
   os.listdir('/')  
 """  
from micropython import const  
import time  
_CMD_TIMEOUT = const(100)  
_R1_IDLE_STATE = const(1 << 0)  
# R1_ERASE_RESET = const(1 << 1)  
_R1_ILLEGAL_COMMAND = const(1 << 2)  
# R1_COM_CRC_ERROR = const(1 << 3)  
# R1_ERASE_SEQUENCE_ERROR = const(1 << 4)  
# R1_ADDRESS_ERROR = const(1 << 5)  
# R1_PARAMETER_ERROR = const(1 << 6)  
_TOKEN_CMD25 = const(0xFC)  
_TOKEN_STOP_TRAN = const(0xFD)  
_TOKEN_DATA = const(0xFE)  
class SDCard:  
   def __init__(self, spi, cs):  
     self.spi = spi  
     self.cs = cs  
     self.cmdbuf = bytearray(6)  
     self.dummybuf = bytearray(512)  
     self.tokenbuf = bytearray(1)  
     for i in range(512):  
       self.dummybuf[i] = 0xFF  
     self.dummybuf_memoryview = memoryview(self.dummybuf)  
     # initialise the card  
     self.init_card()  
   def init_spi(self, baudrate):  
     try:  
       master = self.spi.MASTER  
     except AttributeError:  
       # on ESP8266  
       self.spi.init(baudrate=baudrate, phase=0, polarity=0)  
     else:  
       # on pyboard  
       self.spi.init(master, baudrate=baudrate, phase=0, polarity=0)  
   def init_card(self):  
     # init CS pin  
     self.cs.init(self.cs.OUT, value=1)  
     # init SPI bus; use low data rate for initialisation  
     self.init_spi(100000)  
     # clock card at least 100 cycles with cs high  
     for i in range(16):  
       self.spi.write(b"\xff")  
     # CMD0: init card; should return _R1_IDLE_STATE (allow 5 attempts)  
     for _ in range(5):  
       if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE:  
         break  
     else:  
       raise OSError("no SD card")  
     # CMD8: determine card version  
     r = self.cmd(8, 0x01AA, 0x87, 4)  
     if r == _R1_IDLE_STATE:  
       self.init_card_v2()  
     elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND):  
       self.init_card_v1()  
     else:  
       raise OSError("couldn't determine SD card version")  
     # get the number of sectors  
     # CMD9: response R2 (R1 byte + 16-byte block read)  
     if self.cmd(9, 0, 0, 0, False) != 0:  
       raise OSError("no response from SD card")  
     csd = bytearray(16)  
     self.readinto(csd)  
     if csd[0] & 0xC0 == 0x40: # CSD version 2.0  
       self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024  
     elif csd[0] & 0xC0 == 0x00: # CSD version 1.0 (old, <=2GB)  
       c_size = csd[6] & 0b11 | csd[7] << 2 | (csd[8] & 0b11000000) << 4  
       c_size_mult = ((csd[9] & 0b11) << 1) | csd[10] >> 7  
       self.sectors = (c_size + 1) * (2 ** (c_size_mult + 2))  
     else:  
       raise OSError("SD card CSD format not supported")  
     # print('sectors', self.sectors)  
     # CMD16: set block length to 512 bytes  
     if self.cmd(16, 512, 0) != 0:  
       raise OSError("can't set 512 block size")  
     # set to high data rate now that it's initialised  
     self.init_spi(1320000)  
   def init_card_v1(self):  
     for i in range(_CMD_TIMEOUT):  
       self.cmd(55, 0, 0)  
       if self.cmd(41, 0, 0) == 0:  
         self.cdv = 512  
         # print("[SDCard] v1 card")  
         return  
     raise OSError("timeout waiting for v1 card")  
   def init_card_v2(self):  
     for i in range(_CMD_TIMEOUT):  
       time.sleep_ms(50)  
       self.cmd(58, 0, 0, 4)  
       self.cmd(55, 0, 0)  
       if self.cmd(41, 0x40000000, 0) == 0:  
         self.cmd(58, 0, 0, 4)  
         self.cdv = 1  
         # print("[SDCard] v2 card")  
         return  
     raise OSError("timeout waiting for v2 card")  
   def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False):  
     self.cs(0)  
     # create and send the command  
     buf = self.cmdbuf  
     buf[0] = 0x40 | cmd  
     buf[1] = arg >> 24  
     buf[2] = arg >> 16  
     buf[3] = arg >> 8  
     buf[4] = arg  
     buf[5] = crc  
     self.spi.write(buf)  
     if skip1:  
       self.spi.readinto(self.tokenbuf, 0xFF)  
     # wait for the response (response[7] == 0)  
     for i in range(_CMD_TIMEOUT):  
       self.spi.readinto(self.tokenbuf, 0xFF)  
       response = self.tokenbuf[0]  
       if not (response & 0x80):  
         # this could be a big-endian integer that we are getting here  
         for j in range(final):  
           self.spi.write(b"\xff")  
         if release:  
           self.cs(1)  
           self.spi.write(b"\xff")  
         return response  
     # timeout  
     self.cs(1)  
     self.spi.write(b"\xff")  
     return -1  
   def readinto(self, buf):  
     self.cs(0)  
     # read until start byte (0xff)  
     for i in range(_CMD_TIMEOUT):  
       self.spi.readinto(self.tokenbuf, 0xFF)  
       if self.tokenbuf[0] == _TOKEN_DATA:  
         break  
     else:  
       self.cs(1)  
       raise OSError("timeout waiting for response")  
     # read data  
     mv = self.dummybuf_memoryview  
     if len(buf) != len(mv):  
       mv = mv[: len(buf)]  
     self.spi.write_readinto(mv, buf)  
     # read checksum  
     self.spi.write(b"\xff")  
     self.spi.write(b"\xff")  
     self.cs(1)  
     self.spi.write(b"\xff")  
   def write(self, token, buf):  
     self.cs(0)  
     # send: start of block, data, checksum  
     self.spi.read(1, token)  
     self.spi.write(buf)  
     self.spi.write(b"\xff")  
     self.spi.write(b"\xff")  
     # check the response  
     if (self.spi.read(1, 0xFF)[0] & 0x1F) != 0x05:  
       self.cs(1)  
       self.spi.write(b"\xff")  
       return  
     # wait for write to finish  
     while self.spi.read(1, 0xFF)[0] == 0:  
       pass  
     self.cs(1)  
     self.spi.write(b"\xff")  
   def write_token(self, token):  
     self.cs(0)  
     self.spi.read(1, token)  
     self.spi.write(b"\xff")  
     # wait for write to finish  
     while self.spi.read(1, 0xFF)[0] == 0x00:  
       pass  
     self.cs(1)  
     self.spi.write(b"\xff")  
   def readblocks(self, block_num, buf):  
     nblocks = len(buf) // 512  
     assert nblocks and not len(buf) % 512, "Buffer length is invalid"  
     if nblocks == 1:  
       # CMD17: set read address for single block  
       if self.cmd(17, block_num * self.cdv, 0, release=False) != 0:  
         # release the card  
         self.cs(1)  
         raise OSError(5) # EIO  
       # receive the data and release card  
       self.readinto(buf)  
     else:  
       # CMD18: set read address for multiple blocks  
       if self.cmd(18, block_num * self.cdv, 0, release=False) != 0:  
         # release the card  
         self.cs(1)  
         raise OSError(5) # EIO  
       offset = 0  
       mv = memoryview(buf)  
       while nblocks:  
         # receive the data and release card  
         self.readinto(mv[offset : offset + 512])  
         offset += 512  
         nblocks -= 1  
       if self.cmd(12, 0, 0xFF, skip1=True):  
         raise OSError(5) # EIO  
   def writeblocks(self, block_num, buf):  
     nblocks, err = divmod(len(buf), 512)  
     assert nblocks and not err, "Buffer length is invalid"  
     if nblocks == 1:  
       # CMD24: set write address for single block  
       if self.cmd(24, block_num * self.cdv, 0) != 0:  
         raise OSError(5) # EIO  
       # send the data  
       self.write(_TOKEN_DATA, buf)  
     else:  
       # CMD25: set write address for first block  
       if self.cmd(25, block_num * self.cdv, 0) != 0:  
         raise OSError(5) # EIO  
       # send the data  
       offset = 0
       mv = memoryview(buf)
       while nblocks:
         self.write(_TOKEN_CMD25, mv[offset : offset + 512])
         offset += 512
         nblocks -= 1
       self.write_token(_TOKEN_STOP_TRAN)
   def ioctl(self, op, arg):
     if op == 4: # get number of blocks
       return self.sectors
